package com.zc.manager.device.response;

import com.alibaba.fastjson.JSONObject;
import com.zc.core.Values;
import com.zc.core.device.DeviceOperator;
import com.zc.core.metadata.ConfigPropertyMetadata;
import com.zc.core.metadata.DeviceMetadata;
import com.zc.manager.device.domain.IotDevice;
import com.zc.manager.device.domain.IotDeviceTags;
import com.zc.manager.device.domain.IotProduct;
import com.zc.manager.device.enums.DeviceState;
import com.zc.supports.official.JetLinksDeviceMetadataCodec;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.MapUtils;
import com.zc.manager.device.enums.DeviceType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Getter
@Setter
public class DeviceDetail {

    //设备ID
    @Schema(description = "设备ID")
    private String id;

    //设备名称
    @Schema(description = "设备名称")
    private String name;
    
    //设备编号
    @Schema(description = "设备编号")
    private String code;

    //设备图片
    @Schema(description = "图片地址")
    private String photoUrl;

    //消息协议标识
    @Schema(description = "消息协议ID")
    private String protocol;

    //协议名称
    @Schema(description = "消息协议名称")
    private String protocolName;

    //通信协议
    @Schema(description = "通信协议")
    private String transport;

    //所属机构ID
    @Schema(description = "机构ID")
    private String orgId;

    //所属机构名称
    @Schema(description = "机构名称")
    private String orgName;

    //产品ID
    @Schema(description = "产品ID")
    private String productId;

    //型号名称
    @Schema(description = "产品名称")
    private String productName;

    //设备类型
    @Schema(description = "设备类型")
    private DeviceType deviceType;

    //设备状态
    @Schema(description = "设备状态")
    private DeviceState state;

    //客户端地址 /id:port
    @Schema(description = "ip地址")
    private String address;

    //上线时间
    @Schema(description = "上线时间")
    private long onlineTime;

    //离线时间
    @Schema(description = "离线时间")
    private long offlineTime;

    //创建时间
    @Schema(description = "创建时间")
    private long createTime;

    //激活时间
    @Schema(description = "激活时间")
    private long registerTime;

    //设备元数据
    @Schema(description = "物模型")
    private String metadata;

    //协议拓展数据
    @Schema(description = "协议支持")
    private String protocolStandby;

    //是否为独立物模型
    @Schema(description = "是否为独立物模型")
    private boolean independentMetadata;

    //是否为独立物模型
    @Schema(description = "是否为独立协议支持")
    private boolean independentProtocolStandby;

    //设备配置信息
    @Schema(description = "配置信息")
    private Map<String, Object> configuration = new HashMap<>();

    //已生效的配置信息
    @Schema(description = "已生效的配置信息")
    private Map<String, Object> cachedConfiguration = new HashMap<>();

    //设备单独的配置信息
    @Schema(description = "是否为单独的配置,false表示部分配置信息继承自产品.")
    private boolean aloneConfiguration;

    //父设备ID
    @Schema(description = "父设备ID")
    private String parentId;

    //标签
    @Schema(description = "标签信息")
    private List<IotDeviceTags> tags = new ArrayList<>();

    @Schema(description = "设备描述")
    private String description;

    public DeviceDetail notActive() {

        state = DeviceState.notActive;
        initTags();
        return this;
    }

    private DeviceMetadata decodeMetadata() {
        if (StringUtils.isEmpty(metadata)) {
            return null;
        }
        return JetLinksDeviceMetadataCodec.getInstance().doDecode(metadata);
    }

    private void initTags() {
        DeviceMetadata metadata = decodeMetadata();
        if (null != metadata) {
            with(metadata
                     .getTags()
                     .stream()
                     .map(IotDeviceTags::of)
                     .collect(Collectors.toList()));
        }
    }


    public Mono<DeviceDetail> with(DeviceOperator operator, List<ConfigPropertyMetadata> configs) {
        return Mono
            .zip(
                //T1: 远程地址
                operator.getAddress().defaultIfEmpty("/"),
                //T2: 上线时间
                operator.getOnlineTime().defaultIfEmpty(0L),
                //T3: 离线时间
                operator.getOfflineTime().defaultIfEmpty(0L),
                //T4: 物模型
                operator.getMetadata().switchIfEmpty(Mono.fromSupplier(this::decodeMetadata)),
                //T5: 真实的配置信息
                operator.getSelfConfigs(configs
                                            .stream()
                                            .map(ConfigPropertyMetadata::getProperty)
                                            .collect(Collectors.toList()))
                         .defaultIfEmpty(Values.of(Collections.emptyMap()))
            )
            .doOnNext(tp -> {
                setOnlineTime(tp.getT2());
                setOfflineTime(tp.getT3());
                setAddress(tp.getT1());
                with(tp.getT4()
                       .getTags()
                       .stream()
                       .map(IotDeviceTags::of)
                       .collect(Collectors.toList()));
                Map<String, Object> cachedConfigs = tp.getT5().getAllValues();
                cachedConfiguration.putAll(cachedConfigs);
//                cachedConfigs.forEach(configuration::putIfAbsent);
            })
            .thenReturn(this);
    }

    public synchronized DeviceDetail with(List<IotDeviceTags> tags) {
        if (CollectionUtils.isEmpty(tags)) {
            return this;
        }
        Map<String, IotDeviceTags> map = Stream
            .concat(tags.stream(), this.tags.stream())
            .collect(
                Collectors.toMap(
                    IotDeviceTags::getKey,
                    Function.identity(),
                    (_1, _2) -> StringUtils.hasText(_1.getValue()) ? _1 : _2));

        this.tags = new ArrayList<>(map.values());
        this.tags.sort(Comparator.comparing(IotDeviceTags::getCreateTime));

        return this;
    }

    public DeviceDetail with(IotProduct productEntity) {
        if (productEntity == null) {
            return this;
        }
        if (StringUtils.isEmpty(metadata)) {
            setMetadata(productEntity.getMetadata());
        }
        if (StringUtils.isEmpty(protocolStandby)) {
            setProtocolStandby(productEntity.getProtocolStandby());
        }
        if (CollectionUtils.isEmpty(configuration) && !CollectionUtils.isEmpty(JSONObject.parseObject(productEntity.getConfiguration()))) {
            configuration.putAll(JSONObject.parseObject(productEntity.getConfiguration()));
        }
        setProtocol(productEntity.getProtocolId());
        setTransport(productEntity.getTransportProtocol());
        setProductId(productEntity.getId());
        setProductName(productEntity.getProductName());
        setDeviceType(DeviceType.getDeviceType(productEntity.getDeviceType()));
        setProtocolName(productEntity.getProtocol().getProtocolName());
        return this;
    }

    public DeviceDetail with(IotDevice device) {

        setId(device.getId());
        setName(device.getDeviceName());
        setCode(device.getDeviceCode());
        setState(DeviceState.of(device.getDeviceState()));
        setParentId(device.getParentId());
        setDescription(device.getDescription());

        if (device.getRegistryTime() != null) {
            Optional.of(device.getRegistryTime().getTime())
                    .ifPresent(this::setRegisterTime);
        }

        Optional.of(device.getCreateTime().getTime())
                .ifPresent(this::setCreateTime);

        if (MapUtils.isNotEmpty(JSONObject.parseObject(device.getConfiguration()))) {
            boolean hasConfig = JSONObject.parseObject(device
                .getConfiguration())
                .keySet()
                .stream()
                .map(configuration::get)
                .anyMatch(Objects::nonNull);
            if (hasConfig) {
                setAloneConfiguration(true);
            }
            configuration.putAll(JSONObject.parseObject(device.getConfiguration()));
        }
        if (StringUtils.hasText(device.getDeriveMetadata())) {
            setMetadata(device.getDeriveMetadata());
            setIndependentMetadata(true);
        }

        if (StringUtils.hasText(device.getProtocolStandby())) {
            setProtocolStandby(device.getProtocolStandby());
            setIndependentProtocolStandby(true);
        }

        return this;
    }

}
